﻿using System;
using System.Collections.Generic;
using System.Linq;
using RouteOptimization.ModelObjects;

namespace RouteOptimization.Common.Graph
{
    public class Node
    {
        public Edge Edge { get; set; }

        public Node Parent { get; set; }

        public IList<Node> Childs = new List<Node>();

        /// <summary>
        /// Fill <param name="childs">childs</param> with childs of <param name="node">node</param> that has specified <param name="to">to</param> edge.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="to"></param>
        /// <param name="childs"> </param>
        public void GetChilds(Node node, int to, IList<Node> childs)
        {
            if (node == null)
            {
                return;
            }
            if (node.Edge != null && node.Edge.To != null && node.Edge.To.Id == to)
            {
                
                childs.Add(node);
            }
            foreach (var child in node.Childs)
            {
                GetChilds(child, to, childs);
            }
        }

        /// <summary>
        /// Gets all edges between current node and first child node with specified <param name="to">to</param> edge.
        /// </summary>
        /// <param name="to"></param>
        /// <returns></returns>
        public IList<List<Edge>> GetPathes(int to)
        {
            var childs = new List<Node>();
            GetChilds(this, to, childs);

            var result = new List<List<Edge>>();
            foreach (var child in childs)
            {
                var innerChild = child;
                var tempPath = new List<Edge>();
                var validPath = true;
                while (innerChild.Parent != null)
                {
                    if (!tempPath.Any())
                    {
                        tempPath.Add(innerChild.Edge);
                        innerChild = innerChild.Parent;
                    }
                    else
                    {
                        if (tempPath.Last().To.Routes.Select(x=>x.Id).Intersect(innerChild.Edge.To.Routes.Select(x=>x.Id)).Any())
                        {
                            tempPath.Add(innerChild.Edge);
                            innerChild = innerChild.Parent;
                        }else
                        {
                            //incompatible routes.
                            //if vertex from wich we are going routes contains routes of next vertex.
                            //e.g. if we can change route.
                            validPath = false;
                            break;
                        }
                    }
                }
                if (validPath)
                {
                    result.Add(tempPath);
                }
            }

            return result;
        }

        /// <summary>
        /// Check all childs for specified <param name="expression">expression</param>.
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public bool Contains(Func<Node,bool> expression)
        {
            if (Childs.Any(expression))
            {
                return true;
            }

            foreach (var child in Childs)
            {
                return child.Contains(expression);
            }
            return false;
        }

        public void Add(Edge edge, int destination)
        {
            if (Childs.Any(x => x.Edge.From.Id == edge.To.Id || x.Edge.To.Id == destination))
            {
                return;
            }
            if (!Childs.Any(x => x.Edge.From.Id == edge.From.Id && x.Edge.To.Id == edge.To.Id))
            {
                if (Edge.From.Id == edge.From.Id)
                {
                    Childs.Add(new Node {Edge = edge, Parent = this});
                }
                if (Childs.Any(x => x.Edge.To.Id == edge.From.Id))
                {
                    if (
                        !Childs.First(x => x.Edge.To.Id == edge.From.Id).Childs.Any(
                            x => x.Edge.From.Id == edge.From.Id && x.Edge.To.Id == edge.To.Id))
                    {
                        var parent = Childs.First(x => x.Edge.To.Id == edge.From.Id);
                        parent.Childs.Add(new Node {Edge = edge, Parent = parent});
                    }
                }
            }
            if (Contains(x => x.Edge.To.Id == edge.From.Id))
            {
                foreach (var child in Childs)
                {
                    child.Add(edge, destination);
                }
            }
        }
    }
}